package at.ltasat.train.set;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;

import at.ltasat.ArgumentClazzData;
import at.ltasat.classifier.Classification;
import at.ltasat.classifier.Classifier;
import at.ltasat.classifier.Clazz;
import at.ltasat.classifier.ClazzManager;
import at.ltasat.classifier.Membership;
import at.ltasat.classifier.ObjectFeaturesFactory;
import at.ltasat.util.NullOutputStream;
import at.tasat.Configuration;
import at.tasat.Main;

final class ManualClassifier extends Classifier<byte[], ArgumentClazzData> {

	private final int nIterations;
	private final long maxTime;

	public ManualClassifier(String name, ClazzManager<ArgumentClazzData> classManager, ObjectFeaturesFactory<byte[]> factory, int nIterations, long maxTime) {
		super(name, classManager, factory);
		this.nIterations = nIterations;
		this.maxTime = maxTime;
	}

	@Override
	public Classification<byte[], ArgumentClazzData> classify(final byte[] object) {
		final int[] counts = new int[getClazzManager().size()];
		int count = 0;
		for (int i = 0; i < nIterations; i++) {
			final Clazz<ArgumentClazzData> clazz = myClassify(object);
			if (clazz != null) {
				counts[clazz.getId()]++;
				count++;
			}
		}
		if (count == 0) {
			throw new RuntimeException("Unable to classify!");
		}
		return new Classification<byte[], ArgumentClazzData>(object, toArray(counts, count));
	}

	private Membership<?>[] toArray(int[] counts, int count) {
		final Membership<?>[] memberships = new Membership<?>[counts.length];
		for (int i = 0; i < counts.length; i++) {
			memberships[i] = new Membership<ArgumentClazzData>(getClazzManager().get(i), ((double) counts[i]) / count);
		}
		return memberships;
	}

	private Clazz<ArgumentClazzData> myClassify(final byte[] object) {
		Clazz<ArgumentClazzData> fastest = null;
		long fastestTime = -1;
		for (int i = 0; i < getClazzManager().size(); i++) {
			final Clazz<ArgumentClazzData> clazz = getClazzManager().get(i);
			final long time = run(object, clazz);
			if ((maxTime == 0 || time < maxTime) && (fastest == null || time < fastestTime)) {
				fastest = clazz;
				fastestTime = time;
			}
		}
		return fastest;
	}

	@SuppressWarnings("deprecation")
	private long run(final byte[] object, final Clazz<ArgumentClazzData> clazz) {
		final Configuration config = Configuration.get(clazz.getData().getArgument().split(" "));
		config.setIn(new ByteArrayInputStream(object));
		config.setOut(new NullOutputStream());
		config.setErr(new NullOutputStream());
		final Thread thread = new Thread(new MyRunnable(config));
		long time;
		try {
			System.gc();
			long started = System.currentTimeMillis();
			thread.start();
			thread.join(maxTime);
			time = System.currentTimeMillis() - started;
		} catch (InterruptedException ie) {
			throw new RuntimeException("interrupted");
		} finally {
			if (thread.isAlive()) {
				thread.stop();
			}
		}
		return time;
	}

	private final class MyRunnable implements Runnable {

		private final Configuration config;

		public MyRunnable(final Configuration config) {
			this.config = config;
		}

		@Override
		public void run() {
			Main.main(config);
		}

	}

	@Override
	public void write(OutputStream out) throws IOException {
		// nothing to do
	}

}
